zero chain
V1.1
code:algorithm.rs
public: {
// In order to check the validity of the public key which is used in the encryption
pk_d_sender,
// In order to check the validity of the public key which is used in the encryption
pk_d_recipient,
// The encryption of the transfer value with the sender's public key
Enc_sender(value).cl, // (value)G + (random)pk_d_sender
// The encryption of the transfer value with the recipient's public key
Enc_recipient(value).cl, // (value)G + (random)pk_d_recipient
// The randomness can be reused to encrypt to sender and recipient
Enc(value).cr, // (random)G
// The encryption of the sender's current_balance with the sender's public key
Enc_sender(sender_balance).cl,
Enc_sender(sender_balance).cr,
// the random signature verifying key of the Spend Authorization Signature
rvk,
}
private: {
// The transfer value
value,
// The randomness used to encrypt
random,
// The "remaining" balance of sender i.e. = current_balance - value
remaining_balance,
// The re-randomization parameter for signature verification key
alpha,
// proof generation key
ak,
// The viewing key as a secret key for the encryption scheme
ivk,
}
statement: {
// range proof of the transfer value
value.include(0..2^32-1)
// range proof of the sender's balance
remaining_balance.include(0..2^32-1)
// The validity of public key
pk_d_sender == (ivk)G
// The value encryption validity
Enc_sender(value).cl == (value)G + (random)pk_d_sender
Enc_recipient(value).cl == (value)G + (random)pk_d_recipient
Enc(value).cr == (random)G
// The balance encryption validity.
// It is a bit complicated bacause we can not know the randomness of balance.
// { (current_balance)G - (value)G } + (rbar - random)pk_d_sender
// == (remaining_balance)G + (ivk){ (rbar)G - Enc(value).cr }
// rbar is the current_balance randomness
Enc_sender(sender_balance).cl - Enc_sender(value).cl
== (remaining_balance)G + ivk(Enc_sender(sender_balance).cr - Enc(value).cr)
// Enc_sender(sender_balance).cl + ivk * (random)G
// == Enc_sender(value).cl + (remaining_balance)G + ivk * Enc_sender(sender_balance).cr
// spend authority (proving knowledge of the spendingkey(ask))
rk == ak + alpha * G
// small order check
g_d, ak != O
}
functions: {
require: zk_verify(proof)
require: sig_verify(rk, sign_rsk(SigHash))
require: balance_map_senderaddress_sender == Enc_sender(sender_balance)
Set: balance_map_senderaddress_sender(pk_d_sender) -= Enc(value)
Set: balance_map_recipientaddress_recipient(pk_d_recipient) += Enc(value)
}
storage: {
balance_map_recipient: address_recipient => Enc(balance_recipient)
balance_map_sender: address_sender => Enc(balance_sender)
}
code:tx.rs
pub struct Transaction {
pub sig: Signature, // 64 bytes
pub vk: SigVerificationKey, // 32 bytes
pub proof: Proof, // 192 bytes
pub address_sender: AccountId, // 32 bytes
pub address_recipient: AccountId, // 32 bytes
pub ciphertext_sender: Ciphertext, // 64 bytes
pub ciphertext_recipient: Ciphertext, // 64 bytes
}
Multi-recipient Public-Key Encryption with Shortened Ciphertext
https://link.springer.com/chapter/10.1007/3-540-45664-3_4
ElGamalで、同じrandomnessを用いてsenderとrecipientの公開鍵で暗号化してOK
ciphertext_senderとciphertext_recipientのrandomnessは同じなのでTxのサイズは32bytes減らせる。
viewing keyとproof generation keyを合体。
ciphertext_balanceは乱数部分が入力値とオンチェーン残高で異なるのでTxに含めるのでなくオンチェーン上のciphertext_balanceをそのままzkproofの検証にかける。
The algorithm of Lifted-Elgamal
Encryption
Enc(m) = (mP + r(sP), rP)
where m: message, P: the point on the curve, s: secret key, sP: public key, r: random value
Decryption
(mP + rsP) - s(rP) = mP
m should be 32-bits num.
Homomorphic property
Enc(m) + Enc(m')
= (mP + rsP, rP) + (m'P + r'sP, r'P)
= ((m+m')P + (r + r')sP, (r + r')P)
= Enc(m + m')
Nonce
increment-basedなnonceでなく、epoch-basedなnonceでreplay protectionを行う
つまり、nonce-incrementのバリデーション & nonceを含めたTxの署名検証ではなく、epochごとにそれぞれのアカウントに対し一意に決まるnonceに対してnonce poolのincluded or notのバリデーションを行う。
より具体的には、Nonce = dec_key * G_epochの検証をzk内で行う。
NonceはTx component
dec_keyはprivate input
G_epochはonchain public input
dec_keyを知らないとvalidationをpassできない
同epoch内ではnonce重複 validationによりreplayできない
次以降のepochでは、g_epochが異なるので以前のnonceをreplayすることができない
dec_keyを知っていればG_epochを用いてzk validationを通すことができる
これにより第三者はどのアカウントに対するNonceであるか知ることができない
さらにzk proof自体のreplay protectionも与えることが可能
moneroのようにendless growingなnonce poolではなくepochごとにinit
デメリットはepochごとに1回のTxしか送ることができない
異なるアセットは同じアカウントに対しても異なるnonce poolを持つ
asset-issue時にnonce poolを生成